package edu.scau.ojforwarder.util;

import edu.scau.ojforwarder.bean.HduError;
import edu.scau.ojforwarder.bean.HduStatus;
import edu.scau.ojforwarder.bean.HduSubmission;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.http.util.TextUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;


@Component
public class HduClient {

    private Logger logger = LoggerFactory.getLogger(HduClient.class);

    @Value("${hdu.connectTimeout:0}")
    private int connectTimeout;
    @Value("${hdu.socketTimeout:0}")
    private int socketTimeout;
    @Value("${hdu.username}")
    private String username;
    @Value("${hdu.password}")
    private String password;

    /**
     * 登录HDU
     * @param cid Contest ID
     * @throws ErrorException 网络不通或账号密码不正确时抛出异常
     */
    private void login(String cid) throws ErrorException {
        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
            logger.error(HduError.LOGIN_FAILED.getMsg() + "[账号或密码为空]");
            throw new ErrorException(HduError.LOGIN_FAILED);
        }
        HttpResponse response;
        try {
            response = request(RequestMethod.POST,"http://acm.hdu.edu.cn/userloginex.php?action=login&cid=" + cid).bodyForm(
                    new BasicNameValuePair("username", username),
                    new BasicNameValuePair("userpass", password),
                    new BasicNameValuePair("login", "Sign In")
            ).execute().returnResponse();
        } catch (Exception e) {
            logger.error(HduError.CONNECTION_ERROR.getMsg(), e);
            throw new ErrorException(HduError.CONNECTION_ERROR);
        }

        if (response.getStatusLine().getStatusCode() == 302)
            logger.info("HDU登录成功！");
        else {
            logger.error(HduError.LOGIN_FAILED.getMsg() + "[Contest ID: " + cid + ", username: " + username + ", password: " + password + "]");
            throw new ErrorException(HduError.LOGIN_FAILED);
        }
    }


    /**
     * 提交代码
     * @param submission 需要提交的代码
     * @throws ErrorException 提交失败
     */
    public void submit(HduSubmission submission) throws ErrorException {
        submit(submission, true);
    }

    /**
     * 提交代码
     * @param tryAgain 未登录是否登录后重试
     * @throws ErrorException 提交失败
     */
    private void submit(HduSubmission submission, boolean tryAgain) throws ErrorException {
        HttpResponse response;
        try {
            response = request(RequestMethod.POST, "http://acm.hdu.edu.cn/contests/contest_submit.php?action=submit&cid=" + submission.getCid()).bodyForm(
                    new BasicNameValuePair("problemid", submission.getProblemid()),
                    new BasicNameValuePair("language", submission.getLanguage()),
                    new BasicNameValuePair("usercode", submission.getUsercode())
            ).execute().returnResponse();
        } catch (Exception e) {
            logger.error(HduError.CONNECTION_ERROR.getMsg(), e);
            throw new ErrorException(HduError.CONNECTION_ERROR);
        }

        if (response.getStatusLine().getStatusCode() == 200) {
            String content;
            try {
                content = EntityUtils.toString(response.getEntity());
            } catch (IOException e) {
                logger.error(HduError.READ_RESPONSE_ENTITY_ERROR.getMsg(), e);
                throw new ErrorException(HduError.READ_RESPONSE_ENTITY_ERROR);
            }
            if (content.contains("No such contest."))
                throw new ErrorException(HduError.NO_SUCH_PROBLEM);
            else if (content.contains("length is improper!"))   //HDU规定代码长度必须大于50字节，小于等于65536字节
                throw new ErrorException(HduError.CODE_LENGTH_ERROR);
            else {
                logger.error(HduError.OTHER_SUBMIT_EROOR.getMsg() + "Response content: \n" + content);
                throw new ErrorException(HduError.OTHER_SUBMIT_EROOR);
            }
        } else if (response.getStatusLine().getStatusCode() == 302) {
            String location = response.getHeaders("Location")[0].getValue();
            if (location.contains("userloginex.php")) {    //未登录
                if (tryAgain) {
                    login(submission.getCid());
                    submit(submission, false);
                }
                else
                    throw new ErrorException(HduError.NOT_LOGGED_ERROR);
            }
        } else {
            logger.error(HduError.OTHER_SUBMIT_EROOR + "[HTTP状态码：" + response.getStatusLine().getStatusCode() + "]");
            throw new ErrorException(HduError.OTHER_SUBMIT_EROOR);
        }
    }

    /**
     * 从HDU获取结果
     * @param cid Contest ID
     * @param tryAgain 未登录是否登录后重试
     * @param allPages 为true时获取所有结果，false只获取第一页
     * @return A list of HduStatus
     * @throws ErrorException
     */
    public List<HduStatus> getStatus(String cid, boolean tryAgain, boolean allPages) throws ErrorException {
        List<HduStatus> statusList = new ArrayList<>();
        Set<String> visitedRunId = new HashSet<>();
        for (int page = 1; page == 1 || allPages; page++) {
            String content;
            try {
                content = request(RequestMethod.GET, "http://acm.hdu.edu.cn/contests/contest_status.php?lang=0&status=0&user="
                        + username + "&cid=" + cid + "&page=" + page).execute().returnContent().asString();
            } catch (Exception e) {
                logger.error(HduError.CONNECTION_ERROR.getMsg(), e);
                throw new ErrorException(HduError.CONNECTION_ERROR);
            }

            if (content.contains("Sign In Your Account")) {  //未登录
                if (tryAgain) {
                    login(cid);
                    return getStatus(cid, false, allPages);
                } else
                    throw new ErrorException(HduError.NOT_LOGGED_ERROR);
            } else if (content.contains("No such contest."))
                throw new ErrorException(HduError.NO_SUCH_PROBLEM);

            Document html = Jsoup.parse(content);
            Element el = html.select("tr.table_header").first();
            if (el.nextElementSibling() == null)
                break;

            boolean done = true;
            while (!done && el.nextElementSibling() != null) {
                el = el.nextElementSibling();
                HduStatus status = new HduStatus(cid);
                status.setRunId(el.child(0).text());
                status.setSubmitTime(el.child(1).text());
                status.setStatus(el.child(2).text());
                status.setProId(el.child(3).text());
                status.setExeTime(el.child(4).text());
                status.setExeMemory(el.child(5).text());
                status.setLanguage(el.child(6).text());
                if (visitedRunId.contains(status.getRunId())) {
                    continue;
                } else {
                    statusList.add(status);
                    visitedRunId.add(status.getRunId());
                    done = false;
                }
            }
            if (done) break;
        }
        return statusList;
    }

    /**
     * 获取CE信息
     * @param cid Contest ID
     * @param runId Hdu的runId
     * @return CE的具体信息
     */
    public String getCompileError(String cid, String runId) {
        try {
            String response = request(RequestMethod.GET, "http://acm.hdu.edu.cn/viewerror.php?cid=" + cid + "&rid=" + runId)
                    .execute().returnContent().asString();
            return Jsoup.parse(response).body().getElementsByTag("pre").first().html();
        } catch (Exception e) {
            logger.error("获取编译错误提示失败！", e);
            return null;
        }
    }

    /**
     * 下载题目信息
     * @param cid Contest ID
     * @param pid 题号
     * @param tryAgain 未登录是否登录后重试
     * @return 题目信息
     */
    private String fetchProblemDesc(String cid, String pid, boolean tryAgain) throws ErrorException {
        String html;
        try {
            html = request(RequestMethod.GET, "http://acm.hdu.edu.cn/contests/contest_showproblem.php?cid="
                    + cid + "&pid=" + pid).execute().returnContent().asString();
        } catch (Exception e) {
            logger.error("获取HDU题目信息出错。", e);
            throw new ErrorException(HduError.CONNECTION_ERROR);
        }

        if (html.contains("Sign In Your Account")) {  //未登录
            if (tryAgain) {
                login(cid);
                return fetchProblemDesc(cid, pid, false);
            } else
                throw new ErrorException(HduError.NOT_LOGGED_ERROR);
        } else if (html.contains("No such contest."))
            throw new ErrorException(HduError.NO_SUCH_PROBLEM);

        Document document = Jsoup.parse(html);
        document.outputSettings().prettyPrint(false);
        Element desc = document.getElementById("contest_nav").nextElementSibling();
        return desc.outerHtml();
    }

    public String fetchProblemDesc(String cid, String pid) throws ErrorException {
        return fetchProblemDesc(cid, pid, true);
    }


    public Request request(RequestMethod method, String url) {
        Request request;
        if (method == RequestMethod.POST)
            request = Request.Post(url);
        else
            request = Request.Get(url);
        return request.connectTimeout(connectTimeout).socketTimeout(socketTimeout);
    }
}
